using System;
using System.Collections.Generic;
using System.Text;
using Colman.Rpg.Messaging;
using System.Drawing;

namespace Colman.Rpg.Server.Workflow
{
    /// <summary>
    /// Handles the PVP messages
    /// </summary>
    public static class PvpWorkflowHandler
    {
        /// <summary>
        /// Holds ongoing PVP battels players
        /// </summary>
        private static Dictionary<string, KeyValuePair<string, RequestMessage>> ongoingPvpBattles = new Dictionary<string, KeyValuePair<string, RequestMessage>>();

        /// <summary>
        /// Holds chat rooms players 
        /// </summary>
        private static List<Player> chatRoomPlayers = new List<Player>();

        private static Random random = new Random();

        /// <summary>
        /// creates PVP response messages
        /// </summary>
        /// <param name="e">Holds the request data</param>
        public static void CreateResponses(WorkflowHandlerArgs e)
        {
            // Gets the requester Player object
            Player player = (e.Request.UserName == null ? null : PlayersCache.Get(e.Request.UserName));
            
            if (e.Request is FightRequest)
            {
                // if PVP battle exists
                if (ongoingPvpBattles.ContainsKey(e.Request.UserName))
                {
                    // create PVP battle responses
                    IEnumerable<ResponseMessage> response = CreatePvpFightResponse(e.Request);
                
                    if (response != null)
                        e.Responses.AddRange(response);
                }
            }
            else if (e.Request is ChatMessageRequest)
            {
                // Gets chat request data
                ChatMessageRequest objRequest = (ChatMessageRequest)e.Request;

                // if user is new in chat room then he should be add 
                if (!chatRoomPlayers.Contains(PlayersCache.Get(e.Request.UserName)))
                {
                    chatRoomPlayers.Add(PlayersCache.Get(e.Request.UserName));
                    e.Responses.Add(new GetConnectedUsersResponse(chatRoomPlayers));
                }
               
                if (objRequest.DestinationType == ResponseDestination.Everyone)
                {
                    // Sends message to all chat room players
                    e.Responses.Add(new ChatMessageResponse(objRequest.MessageBody, objRequest.MessageColor, objRequest.UserName, chatRoomPlayers));
                }
                else
                {
                    // Sends message to a specific user in the chat room
                    e.Responses.Add(new ChatMessageResponse(objRequest.MessageBody, objRequest.MessageColor, objRequest.UserName, PlayersCache.Get(objRequest.UserName), PlayersCache.Get(objRequest.DestinationUser)));
                }                
            }
            else if (e.Request is ChatRoomLeaveRequest)
            {
                // Removes user from chat room
                chatRoomPlayers.Remove(PlayersCache.Get(e.Request.UserName));

                // Sends message to all about the unregistered user
                e.Responses.Add(new ChatMessageResponse(e.Request.UserName + " left the chat room", Color.Black, e.Request.UserName, chatRoomPlayers));
                e.Responses.Add(new GetConnectedUsersResponse(chatRoomPlayers));
            }
            else if (e.Request is GetConnectedUsersRequest)
            {
                // Gets all chat room Player objects
                e.Responses.Add(new GetConnectedUsersResponse(chatRoomPlayers));
            }
            else if (e.Request is FightPlayerRequest)
            {
                FightPlayerRequest fpRequest = ((FightPlayerRequest)e.Request);

                switch (fpRequest.RequestOperation)
                {
                    // if new PVP game is requested
                    case PvpFightRequestStage.Ask:
                        // If the player is too powerful or weak for the fight to be fair...
                        Player opponent = PlayersCache.Get(fpRequest.Opponent);

                        if (player.AP < opponent.DP || player.DP > opponent.AP)
                        {
                            e.Responses.Add(new FightPlayerResponse(
                                                PvpFightRequestStage.AutoDecline,
                                                opponent,
                                                player));
                        }
                        else
                        {
                            // Sends the opponent new game request
                            e.Responses.Add(new FightPlayerResponse(
                                                    PvpFightRequestStage.Ask,
                                                    PlayersCache.Get(fpRequest.UserName),
                                                    PlayersCache.Get(fpRequest.Opponent)));
                        }
                        break;
                    // if opponent declined the game
                    case PvpFightRequestStage.Decline:
                        e.Responses.Add(new FightPlayerResponse(
                                                PvpFightRequestStage.Decline, 
                                                PlayersCache.Get(fpRequest.UserName), 
                                                PlayersCache.Get(fpRequest.Opponent)));
                        break;
                    // if opponent accepted the game
                    case PvpFightRequestStage.Accept:
                        e.Responses.Add(new FightPlayerResponse(PvpFightRequestStage.Accept, PlayersCache.Get(fpRequest.UserName), PlayersCache.Get(fpRequest.Opponent)));

                        // update new ongoing battle
                        ongoingPvpBattles.Add(fpRequest.Opponent, new KeyValuePair<string, RequestMessage>(fpRequest.UserName, null));
                        ongoingPvpBattles.Add(fpRequest.UserName, new KeyValuePair<string, RequestMessage>(fpRequest.Opponent, null));

                        // set players status to busy
                        PlayersCache.Get(fpRequest.UserName).IsInBattile = true;
                        PlayersCache.Get(fpRequest.Opponent).IsInBattile = true;
                       
                        e.Responses.Add(new GetConnectedUsersResponse(chatRoomPlayers));
                        break;
                }
            }
        }

        private static IEnumerable<ResponseMessage> CreatePvpFightResponse(RequestMessage request)
        {
            // Updates the requested message in the user's entry 
            ongoingPvpBattles[request.UserName] = new KeyValuePair<string, RequestMessage>(ongoingPvpBattles[request.UserName].Key, request);

            // Gets the opponent name
            string strOpponentKey = ongoingPvpBattles[request.UserName].Key;

            // Checks if the opponent already made his move
            if (ongoingPvpBattles[strOpponentKey].Value == null)
                return null;

            // Makes the move and creates responses
            return MakePvpMove(PlayersCache.Get(strOpponentKey), PlayersCache.Get(request.UserName));
        }

        private static IEnumerable<ResponseMessage> MakePvpMove(Player opponent1, Player opponent2)
        {
            // Getting players' requests
            FightRequest player1FightRequest = (FightRequest)ongoingPvpBattles[opponent1.Name].Value;
            FightRequest player2FightRequest = (FightRequest)ongoingPvpBattles[opponent2.Name].Value;
            int player1ActionResult = 0, player2ActionResult = 0;

            // Defense first.
            if (player1FightRequest.Action == FightActionType.Defend)
            {
                player1ActionResult = opponent1.Defend(opponent2);
            }

            if (player2FightRequest.Action == FightActionType.Defend)
            {
                player2ActionResult = opponent2.Defend(opponent1);
            }

            // Attacks next.
            if (player1FightRequest.Action == FightActionType.Attack)
            {
                player1ActionResult = opponent1.Attack(opponent2);
            }
            
            if (player2FightRequest.Action == FightActionType.Attack)
            {
                player2ActionResult = opponent2.Attack(opponent1);
            }
            
            // Did someone win?
            if (opponent1.HP == 0)
            {
                int xp = opponent2.WinAgainst(opponent1);
                opponent1.LoseBattle();

                return BattleOver(opponent2, opponent1, xp);
            }
            else if (opponent2.HP == 0)
            {
                int xp = opponent1.WinAgainst(opponent2);
                opponent2.LoseBattle();

                return BattleOver(opponent1, opponent2, xp);
            }
            else
            {
                // No one won. Did anyone want to flee?
                if (player1FightRequest.Action == FightActionType.Flee &&
                    player2FightRequest.Action == FightActionType.Flee)
                {
                    opponent1.FleeBattle();
                    opponent2.FleeBattle();

                    return BattleOver(opponent1, opponent2);
                }
                else if (player1FightRequest.Action == FightActionType.Flee)
                {
                    opponent1.FleeBattle();
                    int xp = opponent2.WinAgainst(opponent1);
                    return BattleOver(opponent2, opponent1, xp);
                }
                else if (player2FightRequest.Action == FightActionType.Flee)
                {
                    opponent2.FleeBattle();
                    int xp = opponent1.WinAgainst(opponent1);

                    return BattleOver(opponent2, opponent1, xp);
                }
                else
                {
                    return BattleContinue(opponent1, player1FightRequest.Action, player1ActionResult, opponent2, player2FightRequest.Action, player2ActionResult);
                }
            }
        }

        /// <summary>
        /// Handles battle over with winner
        /// </summary>
        /// <param name="opponentWon">Winner</param>
        /// <param name="opponentLost">Looser</param>
        /// <param name="xp">XP gained</param>
        /// <returns>responses messages</returns>
        private static IEnumerable<ResponseMessage> BattleOver(Player opponentWon, Player opponentLost, int xp)
        {
            opponentWon.HP = opponentWon.MaxHP;
            opponentLost.HP = opponentLost.MaxHP;

            ongoingPvpBattles.Remove(opponentWon.Name);
            ongoingPvpBattles.Remove(opponentLost.Name);
            PlayersCache.Get(opponentWon.Name).IsInBattile = false;
            PlayersCache.Get(opponentLost.Name).IsInBattile = false;

            yield return new PlayerWinsResponse(opponentWon, xp, PlayersCache.Get(opponentWon.Name));
            yield return new PlayerLosesResponse(opponentLost, PlayersCache.Get(opponentLost.Name));
            yield return new GetConnectedUsersResponse(chatRoomPlayers);
            yield return new ChatMessageResponse(opponentWon.Name + " Won Aaginst " + opponentLost.Name, Color.Red, "System");

            PlayersCache.Set(opponentWon);
            PlayersCache.Set(opponentLost);
        }

        /// <summary>
        /// Handles battle over without winner
        /// </summary>
        /// <param name="opponent1Lost">Opponent 1</param>
        /// <param name="opponent2Lost">Opponent 2</param>
        /// <returns>responses messages</returns>
        private static IEnumerable<ResponseMessage> BattleOver(Player opponent1Lost, Player opponent2Lost)
        {
            opponent1Lost.HP = opponent1Lost.MaxHP;
            opponent2Lost.HP = opponent2Lost.MaxHP;

            ongoingPvpBattles.Remove(opponent1Lost.Name);
            ongoingPvpBattles.Remove(opponent2Lost.Name);
            PlayersCache.Get(opponent1Lost.Name).IsInBattile = false;
            PlayersCache.Get(opponent2Lost.Name).IsInBattile = false;

            yield return new PlayerLosesResponse(opponent1Lost, PlayersCache.Get(opponent1Lost.Name));
            yield return new PlayerLosesResponse(opponent2Lost, PlayersCache.Get(opponent2Lost.Name));
            yield return new GetConnectedUsersResponse(chatRoomPlayers);

            PlayersCache.Set(opponent1Lost);
            PlayersCache.Set(opponent2Lost);
        }
        
        /// <summary>
        /// Handles battle move, no winner yet
        /// </summary>
        /// <param name="opponent1">Opponent 1</param>
        /// <param name="opponent1Action">Opponent 1 action made</param>
        /// <param name="opponent1ActionResult">Opponent 1 action result</param>
        /// <param name="opponent2">Opponent 2</param>
        /// <param name="opponent2Action">Opponent 2 action made</param>
        /// <param name="opponent2ActionResult">Opponent 2 action result</param>
        /// <returns></returns>
        private static IEnumerable<ResponseMessage> BattleContinue(Player opponent1, FightActionType opponent1Action, int opponent1ActionResult,
            Player opponent2, FightActionType opponent2Action, int opponent2ActionResult)
        {
            yield return new FightResponse(opponent1, opponent1Action, opponent1ActionResult, opponent2, opponent2Action, opponent2ActionResult);
            yield return new FightResponse(opponent2, opponent2Action, opponent2ActionResult, opponent1, opponent1Action, opponent1ActionResult);
            yield return new GetConnectedUsersResponse(chatRoomPlayers);

            ongoingPvpBattles[opponent1.Name] = new KeyValuePair<string, RequestMessage>(opponent2.Name, null);
            ongoingPvpBattles[opponent2.Name] = new KeyValuePair<string, RequestMessage>(opponent1.Name, null);

            PlayersCache.Set(opponent1);
            PlayersCache.Set(opponent2);
        }
    }
}
